package net.qiqbframework.spring.config;


import net.qiqbframework.common.caching.Cache;
import net.qiqbframework.common.lock.LockFactory;
import net.qiqbframework.config.AggregateConfigurer;
import net.qiqbframework.config.Configurer;
import net.qiqbframework.config.ConfigurerModule;
import net.qiqbframework.modelling.command.CommandTargetResolver;
import net.qiqbframework.modelling.domain.AggregateFactory;
import net.qiqbframework.modelling.repository.AggregateRepository;
import net.qiqbframework.common.utils.AnnotationUtil;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import javax.annotation.Nonnull;
import java.util.Set;

public class SpringAggregateConfigurer<T> implements ConfigurerModule, ApplicationContextAware {

    private final Class<T> aggregateType;
    private final Set<Class<? extends T>> subTypes;

    private String aggregateRepository;

    private String cache;
    private String lockFactory;
    private String commandTargetResolver;
    private boolean filterEventsByType;
    private ApplicationContext applicationContext;
    private String aggregateFactory;

    /**
     * Initializes a {@link ConfigurerModule} for given {@code aggregateType} and possible {@code subTypes}.
     *
     * @param aggregateType The declared type of the aggregate.
     * @param subTypes      The possible subtypes of this aggregate.
     */
    public SpringAggregateConfigurer(Class<T> aggregateType, Set<Class<? extends T>> subTypes) {
        this.aggregateType = aggregateType;
        this.subTypes = subTypes;
    }

    /**
     * Sets the bean name of the {@link AggregateRepository} to configure for this Aggregate.
     *
     * @param aggregateRepository The bean name of the {@link AggregateRepository} for this Aggregate.
     */
    public void setRepository(String aggregateRepository) {
        this.aggregateRepository = aggregateRepository;
    }


    public void setCommandTargetResolver(String commandTargetResolver) {
        this.commandTargetResolver = commandTargetResolver;
    }

    public void setFilterEventsByType(boolean filterEventsByType) {
        this.filterEventsByType = filterEventsByType;
    }

    /**
     * Sets the bean name of the {@link Cache} to use for this Aggregate. Will be ignored if a {@link AggregateRepository} is
     * also configured.
     *
     * @param cache The bean name of the {@link Cache} to use for this Aggregate.
     */
    public void setCache(String cache) {
        this.cache = cache;
    }

    /**
     * Sets the bean name of the {@link LockFactory} to use for this Aggregate. Will be ignored if a {@link AggregateRepository}
     * is also configured.
     *
     * @param lockFactory The bean name of the {@link LockFactory} to use for this Aggregate.
     */
    public void setLockFactory(String lockFactory) {
        this.lockFactory = lockFactory;
    }

    /**
     * Sets the bean name of the {@link AggregateFactory} to use for this Aggregate. Will be ignored if a {@link
     * AggregateRepository} is also configured.
     *
     * @param aggregateFactory The bean name of the AggregateFactory to use for this Aggregate.
     */
    public void setAggregateFactory(String aggregateFactory) {
        this.aggregateFactory = aggregateFactory;
    }

    @Override
    public void configureModule(@Nonnull Configurer configurer) {
        AggregateConfigurer<T> aggregateConfigurer = AggregateConfigurer.defaultConfiguration(aggregateType)
                .withSubtypes(subTypes);

        if (aggregateRepository != null) {
            // noinspection unchecked
            aggregateConfigurer.configureRepository(
                    c -> applicationContext.getBean(aggregateRepository, AggregateRepository.class)
            );
        }


        if (commandTargetResolver != null) {
            aggregateConfigurer.configureCommandTargetResolver(
                    c -> applicationContext.getBean(commandTargetResolver, CommandTargetResolver.class)
            );
        }
        if (cache != null) {
            aggregateConfigurer.configureCache(c -> applicationContext.getBean(cache, Cache.class));
        }
        if (lockFactory != null) {
            aggregateConfigurer.configureLockFactory(c -> applicationContext.getBean(lockFactory, LockFactory.class));
        }
        if (aggregateFactory != null) {
            // noinspection unchecked
            aggregateConfigurer.configureAggregateFactory(
                    c -> applicationContext.getBean(aggregateFactory, AggregateFactory.class)
            );
        }
        aggregateConfigurer.configureFilterEventsByType(c -> filterEventsByType);
        configurer.registerConfigureAggregate(aggregateConfigurer);
    }

    private boolean isEntityManagerAnnotationPresent(Class<T> type) {
        return AnnotationUtil.isAnnotationPresent(type, "javax.persistence.Entity")
                || AnnotationUtil.isAnnotationPresent(type, "jakarta.persistence.Entity");
    }

    @Override
    public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
